/*
 * cash.c
 * 由 D'Arcy J.M. Cain 编写
 * darcy@druid.net
 * http://www.druid.net/darcy/
 *
 * 函数允许正常输入和输出货币，但将其存储
 * 和处理为 64 位整数
 *
 * 这个文件的稍作修改版本以及对
 * 工作原理的讨论可以在 Dale Schumacher 的书 "Software Solutions in C"
 * 中找到，Academic Press，ISBN：0-12-632360-7，除了这个版本处理 64 位数字，
 * 因此可以容纳高达 $92,233,720,368,547,758.07 的值。
 *
 * src/backend/utils/adt/cash.c
 */

#include "postgres.h"

#include <limits.h>
#include <ctype.h>
#include <math.h>

#include "common/int.h"
#include "libpq/pqformat.h"
#include "utils/builtins.h"
#include "utils/cash.h"
#include "utils/float.h"
#include "utils/numeric.h"
#include "utils/pg_locale.h"


/*************************************************************************
 * 私有例程
 ************************************************************************/

static const char * fc_num_word(Cash fc_value)
{
	static char fc_buf[128];
	static const char *const fc_small[] = {
		"zero", "one", "two", "three", "four", "five", "six", "seven",
		"eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
		"fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
		"thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
	};
	const char *const *fc_big = fc_small + 18;
	int			fc_tu = fc_value % 100;

	/* 先处理简单情况 */
	if (fc_value <= 20)
		return fc_small[fc_value];

	/* 它是 100 的偶数倍吗？ */
	if (!fc_tu)
	{
		sprintf(fc_buf, "%s hundred", fc_small[fc_value / 100]);
		return fc_buf;
	}

	/* 超过 99 吗？ */
	if (fc_value > 99)
	{
		/* 它是 10 的偶数倍，且不是 10 吗？ */
		if (fc_value % 10 == 0 && fc_tu > 10)
			sprintf(fc_buf, "%s hundred %s",
					fc_small[fc_value / 100], fc_big[fc_tu / 10]);
		else if (fc_tu < 20)
			sprintf(fc_buf, "%s hundred and %s",
					fc_small[fc_value / 100], fc_small[fc_tu]);
		else
			sprintf(fc_buf, "%s hundred %s %s",
					fc_small[fc_value / 100], fc_big[fc_tu / 10], fc_small[fc_tu % 10]);
	}
	else
	{
		/* 它是 10 的偶数倍，且不是 10 吗？ */
		if (fc_value % 10 == 0 && fc_tu > 10)
			sprintf(fc_buf, "%s", fc_big[fc_tu / 10]);
		else if (fc_tu < 20)
			sprintf(fc_buf, "%s", fc_small[fc_tu]);
		else
			sprintf(fc_buf, "%s %s", fc_big[fc_tu / 10], fc_small[fc_tu % 10]);
	}

	return fc_buf;
}								/* num_word() */

static inline Cash fc_cash_pl_cash(Cash fc_c1, Cash fc_c2)
{
	Cash		fc_res;

	if (unlikely(pg_add_s64_overflow(fc_c1, fc_c2, &fc_res)))
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("money out of range")));

	return fc_res;
}

static inline Cash fc_cash_mi_cash(Cash fc_c1, Cash fc_c2)
{
	Cash		fc_res;

	if (unlikely(pg_sub_s64_overflow(fc_c1, fc_c2, &fc_res)))
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("money out of range")));

	return fc_res;
}

static inline Cash fc_cash_mul_float8(Cash fc_c, float8 fc_f)
{
	float8		fc_res = rint(float8_mul((float8) fc_c, fc_f));

	if (unlikely(isnan(fc_res) || !FLOAT8_FITS_IN_INT64(fc_res)))
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("money out of range")));

	return (Cash) fc_res;
}

static inline Cash fc_cash_div_float8(Cash fc_c, float8 fc_f)
{
	float8		fc_res = rint(float8_div((float8) fc_c, fc_f));

	if (unlikely(isnan(fc_res) || !FLOAT8_FITS_IN_INT64(fc_res)))
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("money out of range")));

	return (Cash) fc_res;
}

static inline Cash fc_cash_mul_int64(Cash fc_c, int64 fc_i)
{
	Cash		fc_res;

	if (unlikely(pg_mul_s64_overflow(fc_c, fc_i, &fc_res)))
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("money out of range")));

	return fc_res;
}

static inline Cash fc_cash_div_int64(Cash fc_c, int64 fc_i)
{
	if (unlikely(fc_i == 0))
		ereport(ERROR,
				(errcode(ERRCODE_DIVISION_BY_ZERO),
				 errmsg("division by zero")));

	return fc_c / fc_i;
}

/* cash_in()
 * 将字符串转换为现金数据类型。
 * 格式为 [$]###[,]###[.##]
 * 示例： 123.45 $123.45 $123,456.78
 *
 */
Datum cash_in(PG_FUNCTION_ARGS)
{
	char	   *fc_str = PG_GETARG_CSTRING(0);
	Cash		fc_result;
	Cash		fc_value = 0;
	Cash		fc_dec = 0;
	Cash		fc_sgn = 1;
	bool		fc_seen_dot = false;
	const char *fc_s = fc_str;
	int			fc_fpoint;
	char		fc_dsymbol;
	const char *fc_ssymbol,
			   *fc_psymbol,
			   *fc_nsymbol,
			   *fc_csymbol;
	struct lconv *fc_lconvert = PGLC_localeconv();

	/*
	 * frac_digits 在某些地区会是 CHAR_MAX，特别是 C。然而，仅测试 == CHAR_MAX 是有风险的，
	 * 因为像 gcc 这样的编译器“友好地”允许您修改
	 * 平台标准定义，即 char 是有符号的还是无符号的。
	 * 如果我们不幸地使用了非标准的 -fsigned-char 或 -funsigned-char 开关编译，
	 * 那么我们的 CHAR_MAX 概念将与 libc 不一致。最安全的做法是不测试 CHAR_MAX，
	 * 而是在可能的 frac_digits 值上施加范围检查。
	 */
	fc_fpoint = fc_lconvert->frac_digits;
	if (fc_fpoint < 0 || fc_fpoint > 10)
		fc_fpoint = 2;				/* 在这种情况下的最佳猜测，我认为 */

	/* 我们限制 dsymbol 为单个字节，而不限制其他符号 */
	if (*fc_lconvert->mon_decimal_point != '\0' &&
		fc_lconvert->mon_decimal_point[1] == '\0')
		fc_dsymbol = *fc_lconvert->mon_decimal_point;
	else
		fc_dsymbol = '.';
	if (*fc_lconvert->mon_thousands_sep != '\0')
		fc_ssymbol = fc_lconvert->mon_thousands_sep;
	else						/* ssymbol 不应等于 dsymbol */
		fc_ssymbol = (fc_dsymbol != ',') ? "," : ".";
	fc_csymbol = (*fc_lconvert->currency_symbol != '\0') ? fc_lconvert->currency_symbol : "$";
	fc_psymbol = (*fc_lconvert->positive_sign != '\0') ? fc_lconvert->positive_sign : "+";
	fc_nsymbol = (*fc_lconvert->negative_sign != '\0') ? fc_lconvert->negative_sign : "-";

#ifdef CASHDEBUG
	printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
		   fc_fpoint, fc_dsymbol, fc_ssymbol, fc_csymbol, fc_psymbol, fc_nsymbol);
#endif

	/* 我们需要添加各种检查。现在只需 */
	/* 去除所有前导空格和任何前导货币符号 */
	while (isspace((unsigned char) *fc_s))
		fc_s++;
	if (strncmp(fc_s, fc_csymbol, strlen(fc_csymbol)) == 0)
		fc_s += strlen(fc_csymbol);
	while (isspace((unsigned char) *fc_s))
		fc_s++;

#ifdef CASHDEBUG
	printf("cashin- string is '%s'\n", fc_s);
#endif

	/* 前面的负号或括号表示负数 */
	/* 再次，需要更好的启发式 */
	/* XXX - 不能正确检查平衡括号 - djmc */
	if (strncmp(fc_s, fc_nsymbol, strlen(fc_nsymbol)) == 0)
	{
		fc_sgn = -1;
		fc_s += strlen(fc_nsymbol);
	}
	else if (*fc_s == '(')
	{
		fc_sgn = -1;
		fc_s++;
	}
	else if (strncmp(fc_s, fc_psymbol, strlen(fc_psymbol)) == 0)
		fc_s += strlen(fc_psymbol);

#ifdef CASHDEBUG
	printf("cashin- string is '%s'\n", fc_s);
#endif

	/* 也允许在符号后面有空格和货币符号 */
	while (isspace((unsigned char) *fc_s))
		fc_s++;
	if (strncmp(fc_s, fc_csymbol, strlen(fc_csymbol)) == 0)
		fc_s += strlen(fc_csymbol);
	while (isspace((unsigned char) *fc_s))
		fc_s++;

#ifdef CASHDEBUG
	printf("cashin- string is '%s'\n", fc_s);
#endif

	/*
	 * 我们在“value”中累积绝对金额，然后在最后应用符号。 
	 * （符号可以出现在数字之前或之后，因此如果采用其他方式会更复杂。） 
	 * 由于负号整数的范围更大，我们在负数中构建“value”，然后在最后翻转符号，必要时捕获最小负数溢出。
	 */

	for (; *fc_s; fc_s++)
	{
		/*
		 * 我们寻找数字，只要我们发现的十进制位数少于要求的位数。
		 */
		if (isdigit((unsigned char) *fc_s) && (!fc_seen_dot || fc_dec < fc_fpoint))
		{
			int8		fc_digit = *fc_s - '0';

			if (pg_mul_s64_overflow(fc_value, 10, &fc_value) ||
				pg_sub_s64_overflow(fc_value, fc_digit, &fc_value))
				ereport(ERROR,
						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
						 errmsg("value \"%s\" is out of range for type %s",
								fc_str, "money")));

			if (fc_seen_dot)
				fc_dec++;
		}
		/* 小数点？然后开始计数分数... */
		else if (*fc_s == fc_dsymbol && !fc_seen_dot)
		{
			fc_seen_dot = true;
		}
		/* 如果是“千位”分隔符则忽略，否则我们完成了 */
		else if (strncmp(fc_s, fc_ssymbol, strlen(fc_ssymbol)) == 0)
			fc_s += strlen(fc_ssymbol) - 1;
		else
			break;
	}

	/* 如果还有另一位数字则四舍五入 */
	if (isdigit((unsigned char) *fc_s) && *fc_s >= '5')
	{
		/* 记住我们是在负数中构建值 */
		if (pg_sub_s64_overflow(fc_value, 1, &fc_value))
			ereport(ERROR,
					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
					 errmsg("value \"%s\" is out of range for type %s",
							fc_str, "money")));
	}

	/* 调整以适应少于要求的十进制位 */
	for (; fc_dec < fc_fpoint; fc_dec++)
	{
		if (pg_mul_s64_overflow(fc_value, 10, &fc_value))
			ereport(ERROR,
					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
					 errmsg("value \"%s\" is out of range for type %s",
							fc_str, "money")));
	}

	/*
	 * 应该只有后缀数字，后面跟着空格、右括号、 
	 * 后缀符号和/或后缀货币符号
	 */
	while (isdigit((unsigned char) *fc_s))
		fc_s++;

	while (*fc_s)
	{
		if (isspace((unsigned char) *fc_s) || *fc_s == ')')
			fc_s++;
		else if (strncmp(fc_s, fc_nsymbol, strlen(fc_nsymbol)) == 0)
		{
			fc_sgn = -1;
			fc_s += strlen(fc_nsymbol);
		}
		else if (strncmp(fc_s, fc_psymbol, strlen(fc_psymbol)) == 0)
			fc_s += strlen(fc_psymbol);
		else if (strncmp(fc_s, fc_csymbol, strlen(fc_csymbol)) == 0)
			fc_s += strlen(fc_csymbol);
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					 errmsg("invalid input syntax for type %s: \"%s\"",
							"money", fc_str)));
	}

	/*
	 * 如果值应该是正数，则翻转符号，但要检查最小负数。
	 */
	if (fc_sgn > 0)
	{
		if (fc_value == PG_INT64_MIN)
			ereport(ERROR,
					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
					 errmsg("value \"%s\" is out of range for type %s",
							fc_str, "money")));
		fc_result = -fc_value;
	}
	else
		fc_result = fc_value;

#ifdef CASHDEBUG
	printf("cashin- result is " INT64_FORMAT "\n", fc_result);
#endif

	PG_RETURN_CASH(fc_result);
}


/* cash_out()
 * 将现金转换为美元和分的表示形式，使用
 * lc_monetary 区域设置的格式。
 */
Datum cash_out(PG_FUNCTION_ARGS)
{
	Cash		fc_value = PG_GETARG_CASH(0);
	char	   *fc_result;
	char		fc_buf[128];
	char	   *fc_bufptr;
	int			fc_digit_pos;
	int			fc_points,
				fc_mon_group;
	char		fc_dsymbol;
	const char *fc_ssymbol,
			   *fc_csymbol,
			   *fc_signsymbol;
	char		fc_sign_posn,
				fc_cs_precedes,
				fc_sep_by_space;
	struct lconv *fc_lconvert = PGLC_localeconv();

	/* 参见 cash_in() 中关于 frac_digits 的注释 */
	fc_points = fc_lconvert->frac_digits;
	if (fc_points < 0 || fc_points > 10)
		fc_points = 2;				/* 在这种情况下的最佳猜测，我认为 */

	/*
	 * 与 frac_digits 一样，必须对 mon_grouping 应用范围检查以避免 
	 * 被变体 CHAR_MAX 值欺骗。
	 */
	fc_mon_group = *fc_lconvert->mon_grouping;
	if (fc_mon_group <= 0 || fc_mon_group > 6)
		fc_mon_group = 3;

	/* 我们限制 dsymbol 为单个字节，而不限制其他符号 */
	if (*fc_lconvert->mon_decimal_point != '\0' &&
		fc_lconvert->mon_decimal_point[1] == '\0')
		fc_dsymbol = *fc_lconvert->mon_decimal_point;
	else
		fc_dsymbol = '.';
	if (*fc_lconvert->mon_thousands_sep != '\0')
		fc_ssymbol = fc_lconvert->mon_thousands_sep;
	else						/* ssymbol 不应等于 dsymbol */
		fc_ssymbol = (fc_dsymbol != ',') ? "," : ".";
	fc_csymbol = (*fc_lconvert->currency_symbol != '\0') ? fc_lconvert->currency_symbol : "$";

	if (fc_value < 0)
	{
		/* 为数字重构循环使金额为正 */
		fc_value = -fc_value;
		/* 设置格式数据 */
		fc_signsymbol = (*fc_lconvert->negative_sign != '\0') ? fc_lconvert->negative_sign : "-";
		fc_sign_posn = fc_lconvert->n_sign_posn;
		fc_cs_precedes = fc_lconvert->n_cs_precedes;
		fc_sep_by_space = fc_lconvert->n_sep_by_space;
	}
	else
	{
		fc_signsymbol = fc_lconvert->positive_sign;
		fc_sign_posn = fc_lconvert->p_sign_posn;
		fc_cs_precedes = fc_lconvert->p_cs_precedes;
		fc_sep_by_space = fc_lconvert->p_sep_by_space;
	}

	/* 我们在 buf[] 中从右到左构建数字+小数点+分隔符字符串 */
	fc_bufptr = fc_buf + sizeof(fc_buf) - 1;
	*fc_bufptr = '\0';

	/*
	 * 生成数字，直到没有非零数字并且我们至少在小数点左侧发出了一个数字。 
	 * digit_pos 是当前数字位置，零是小数点左侧的数字，向右增加。
	 */
	fc_digit_pos = fc_points;
	do
	{
		if (fc_points && fc_digit_pos == 0)
		{
			/* 插入小数点，但如果值不能是小数则不插入 */
			*(--fc_bufptr) = fc_dsymbol;
		}
		else if (fc_digit_pos < 0 && (fc_digit_pos % fc_mon_group) == 0)
		{
			/* 插入千位分隔符，但仅在小数点左侧 */
			fc_bufptr -= strlen(fc_ssymbol);
			memcpy(fc_bufptr, fc_ssymbol, strlen(fc_ssymbol));
		}

		*(--fc_bufptr) = ((uint64) fc_value % 10) + '0';
		fc_value = ((uint64) fc_value) / 10;
		fc_digit_pos--;
	} while (fc_value || fc_digit_pos >= 0);

	/*----------
	 * 现在，将货币符号和符号符号按正确顺序附加。
	 *
	 * POSIX 规范定义了这些值来控制此代码：
	 *
	 * p/n_sign_posn:
	 *	0	括号包围数量和货币符号。
	 *	1	符号字符串在数量和货币符号之前。
	 *	2	符号字符串在数量和货币符号之后。
	 *	3	符号字符串在货币符号之前。
	 *	4	符号字符串在货币符号之后。
	 *
	 * p/n_cs_precedes: 0表示货币符号在值之后，否则在值之前。
	 *
	 * p/n_sep_by_space:
	 *	0	没有 <space> 分隔货币符号和价值。
	 *	1	如果货币符号和符号字符串是相邻的，则一个 <space>
	 *		将它们与价值分开；否则，一个 <space> 将货币符号与价值分开。
	 *	2	如果货币符号和符号字符串是相邻的，则一个 <space>
	 *		将它们分开；否则，一个 <space> 将符号字符串与价值分开。
	 *----------
	 */
	switch (fc_sign_posn)
	{
		case 0:
			if (fc_cs_precedes)
				fc_result = psprintf("(%s%s%s)",
								  fc_csymbol,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_bufptr);
			else
				fc_result = psprintf("(%s%s%s)",
								  fc_bufptr,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_csymbol);
			break;
		case 1:
		default:
			if (fc_cs_precedes)
				fc_result = psprintf("%s%s%s%s%s",
								  fc_signsymbol,
								  (fc_sep_by_space == 2) ? " " : "",
								  fc_csymbol,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_bufptr);
			else
				fc_result = psprintf("%s%s%s%s%s",
								  fc_signsymbol,
								  (fc_sep_by_space == 2) ? " " : "",
								  fc_bufptr,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_csymbol);
			break;
		case 2:
			if (fc_cs_precedes)
				fc_result = psprintf("%s%s%s%s%s",
								  fc_csymbol,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_bufptr,
								  (fc_sep_by_space == 2) ? " " : "",
								  fc_signsymbol);
			else
				fc_result = psprintf("%s%s%s%s%s",
								  fc_bufptr,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_csymbol,
								  (fc_sep_by_space == 2) ? " " : "",
								  fc_signsymbol);
			break;
		case 3:
			if (fc_cs_precedes)
				fc_result = psprintf("%s%s%s%s%s",
								  fc_signsymbol,
								  (fc_sep_by_space == 2) ? " " : "",
								  fc_csymbol,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_bufptr);
			else
				fc_result = psprintf("%s%s%s%s%s",
								  fc_bufptr,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_signsymbol,
								  (fc_sep_by_space == 2) ? " " : "",
								  fc_csymbol);
			break;
		case 4:
			if (fc_cs_precedes)
				fc_result = psprintf("%s%s%s%s%s",
								  fc_csymbol,
								  (fc_sep_by_space == 2) ? " " : "",
								  fc_signsymbol,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_bufptr);
			else
				fc_result = psprintf("%s%s%s%s%s",
								  fc_bufptr,
								  (fc_sep_by_space == 1) ? " " : "",
								  fc_csymbol,
								  (fc_sep_by_space == 2) ? " " : "",
								  fc_signsymbol);
			break;
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		cash_recv			- 将外部二进制格式转换为现金
 */
Datum cash_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);

	PG_RETURN_CASH((Cash) pq_getmsgint64(fc_buf));
}

/*
 *		cash_send			- 将现金转换为二进制格式
 */
Datum cash_send(PG_FUNCTION_ARGS)
{
	Cash		fc_arg1 = PG_GETARG_CASH(0);
	StringInfoData fc_buf;

	pq_begintypsend(&fc_buf);
	pq_sendint64(&fc_buf, fc_arg1);
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}

/*
 * 比较函数
 */

Datum cash_eq(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	PG_RETURN_BOOL(fc_c1 == fc_c2);
}

Datum cash_ne(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	PG_RETURN_BOOL(fc_c1 != fc_c2);
}

Datum cash_lt(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	PG_RETURN_BOOL(fc_c1 < fc_c2);
}

Datum cash_le(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	PG_RETURN_BOOL(fc_c1 <= fc_c2);
}

Datum cash_gt(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	PG_RETURN_BOOL(fc_c1 > fc_c2);
}

Datum cash_ge(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	PG_RETURN_BOOL(fc_c1 >= fc_c2);
}

Datum cash_cmp(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	if (fc_c1 > fc_c2)
		PG_RETURN_INT32(1);
	else if (fc_c1 == fc_c2)
		PG_RETURN_INT32(0);
	else
		PG_RETURN_INT32(-1);
}


/* cash_pl()
 * 将两个现金值相加。
 */
Datum cash_pl(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	PG_RETURN_CASH(fc_cash_pl_cash(fc_c1, fc_c2));
}


/* cash_mi()
 * 将两个现金值相减。
 */
Datum cash_mi(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);

	PG_RETURN_CASH(fc_cash_mi_cash(fc_c1, fc_c2));
}


/* cash_div_cash()
 * 用现金除以现金，返回 float8。
 */
Datum cash_div_cash(PG_FUNCTION_ARGS)
{
	Cash		fc_dividend = PG_GETARG_CASH(0);
	Cash		fc_divisor = PG_GETARG_CASH(1);
	float8		fc_quotient;

	if (fc_divisor == 0)
		ereport(ERROR,
				(errcode(ERRCODE_DIVISION_BY_ZERO),
				 errmsg("division by zero")));

	fc_quotient = (float8) fc_dividend / (float8) fc_divisor;
	PG_RETURN_FLOAT8(fc_quotient);
}


/* cash_mul_flt8()
 * 将现金乘以 float8。
 */
Datum cash_mul_flt8(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	float8		fc_f = PG_GETARG_FLOAT8(1);

	PG_RETURN_CASH(fc_cash_mul_float8(fc_c, fc_f));
}


/* flt8_mul_cash()
 * 将 float8 乘以现金。
 */
Datum flt8_mul_cash(PG_FUNCTION_ARGS)
{
	float8		fc_f = PG_GETARG_FLOAT8(0);
	Cash		fc_c = PG_GETARG_CASH(1);

	PG_RETURN_CASH(fc_cash_mul_float8(fc_c, fc_f));
}


/* cash_div_flt8()
 * 用现金除以 float8。
 */
Datum cash_div_flt8(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	float8		fc_f = PG_GETARG_FLOAT8(1);

	PG_RETURN_CASH(fc_cash_div_float8(fc_c, fc_f));
}


/* cash_mul_flt4()
 * 将现金乘以 float4。
 */
Datum cash_mul_flt4(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	float4		fc_f = PG_GETARG_FLOAT4(1);

	PG_RETURN_CASH(fc_cash_mul_float8(fc_c, (float8) fc_f));
}


/* flt4_mul_cash()
 * 将 float4 乘以现金。
 */
Datum flt4_mul_cash(PG_FUNCTION_ARGS)
{
	float4		fc_f = PG_GETARG_FLOAT4(0);
	Cash		fc_c = PG_GETARG_CASH(1);

	PG_RETURN_CASH(fc_cash_mul_float8(fc_c, (float8) fc_f));
}


/* cash_div_flt4()
 * 用现金除以 float4。
 *
 */
Datum cash_div_flt4(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	float4		fc_f = PG_GETARG_FLOAT4(1);

	PG_RETURN_CASH(fc_cash_div_float8(fc_c, (float8) fc_f));
}


/* cash_mul_int8()
 * 将现金乘以 int8。
 */
Datum cash_mul_int8(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	int64		fc_i = PG_GETARG_INT64(1);

	PG_RETURN_CASH(fc_cash_mul_int64(fc_c, fc_i));
}


/* int8_mul_cash()
 * 将 int8 乘以现金。
 */
Datum int8_mul_cash(PG_FUNCTION_ARGS)
{
	int64		fc_i = PG_GETARG_INT64(0);
	Cash		fc_c = PG_GETARG_CASH(1);

	PG_RETURN_CASH(fc_cash_mul_int64(fc_c, fc_i));
}

/* cash_div_int8()
 * 用现金除以 8 字节整数。
 */
Datum cash_div_int8(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	int64		fc_i = PG_GETARG_INT64(1);

	PG_RETURN_CASH(fc_cash_div_int64(fc_c, fc_i));
}


/* cash_mul_int4()
 * 将现金乘以 int4。
 */
Datum cash_mul_int4(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	int32		fc_i = PG_GETARG_INT32(1);

	PG_RETURN_CASH(fc_cash_mul_int64(fc_c, (int64) fc_i));
}


/* int4_mul_cash()
 * 将 int4 乘以现金。
 */
Datum int4_mul_cash(PG_FUNCTION_ARGS)
{
	int32		fc_i = PG_GETARG_INT32(0);
	Cash		fc_c = PG_GETARG_CASH(1);

	PG_RETURN_CASH(fc_cash_mul_int64(fc_c, (int64) fc_i));
}


/* cash_div_int4()
 * 用现金除以 4 字节整数。
 *
 */
Datum cash_div_int4(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	int32		fc_i = PG_GETARG_INT32(1);

	PG_RETURN_CASH(fc_cash_div_int64(fc_c, (int64) fc_i));
}


/* cash_mul_int2()
 * 将现金乘以 int2。
 */
Datum cash_mul_int2(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	int16		fc_s = PG_GETARG_INT16(1);

	PG_RETURN_CASH(fc_cash_mul_int64(fc_c, (int64) fc_s));
}

/* int2_mul_cash()
 * 将 int2 乘以现金。
 */
Datum int2_mul_cash(PG_FUNCTION_ARGS)
{
	int16		fc_s = PG_GETARG_INT16(0);
	Cash		fc_c = PG_GETARG_CASH(1);

	PG_RETURN_CASH(fc_cash_mul_int64(fc_c, (int64) fc_s));
}

/* cash_div_int2()
 * 用现金除以 int2。
 *
 */
Datum cash_div_int2(PG_FUNCTION_ARGS)
{
	Cash		fc_c = PG_GETARG_CASH(0);
	int16		fc_s = PG_GETARG_INT16(1);

	PG_RETURN_CASH(fc_cash_div_int64(fc_c, (int64) fc_s));
}

/* cashlarger()
 * 返回两个现金值中较大的一个。
 */
Datum cashlarger(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);
	Cash		fc_result;

	fc_result = (fc_c1 > fc_c2) ? fc_c1 : fc_c2;

	PG_RETURN_CASH(fc_result);
}

/* cashsmaller()
 * 返回两个现金值中较小的一个。
 */
Datum cashsmaller(PG_FUNCTION_ARGS)
{
	Cash		fc_c1 = PG_GETARG_CASH(0);
	Cash		fc_c2 = PG_GETARG_CASH(1);
	Cash		fc_result;

	fc_result = (fc_c1 < fc_c2) ? fc_c1 : fc_c2;

	PG_RETURN_CASH(fc_result);
}

/* cash_words()
 * 这也将 int4 转换为使用单词表示的形式
 * 显然非常以北美为中心 - 抱歉
 */
Datum cash_words(PG_FUNCTION_ARGS)
{
	Cash		fc_value = PG_GETARG_CASH(0);
	uint64		fc_val;
	char		fc_buf[256];
	char	   *fc_p = fc_buf;
	Cash		fc_m0;
	Cash		fc_m1;
	Cash		fc_m2;
	Cash		fc_m3;
	Cash		fc_m4;
	Cash		fc_m5;
	Cash		fc_m6;

	/* 处理正数 */
	if (fc_value < 0)
	{
		fc_value = -fc_value;
		strcpy(fc_buf, "minus ");
		fc_p += 6;
	}
	else
		fc_buf[0] = '\0';

	/* 现在视为无符号，以避免在 INT_MIN 时出问题 */
	fc_val = (uint64) fc_value;

	fc_m0 = fc_val % INT64CONST(100); /* cents */
	fc_m1 = (fc_val / INT64CONST(100)) % 1000;	/* hundreds */
	fc_m2 = (fc_val / INT64CONST(100000)) % 1000; /* 千 */
	fc_m3 = (fc_val / INT64CONST(100000000)) % 1000;	/* millions */
	fc_m4 = (fc_val / INT64CONST(100000000000)) % 1000;	/* billions */
	fc_m5 = (fc_val / INT64CONST(100000000000000)) % 1000;	/* 万亿 */
	fc_m6 = (fc_val / INT64CONST(100000000000000000)) % 1000; /* 千万亿 */

	if (fc_m6)
	{
		strcat(fc_buf, fc_num_word(fc_m6));
		strcat(fc_buf, " quadrillion ");
	}

	if (fc_m5)
	{
		strcat(fc_buf, fc_num_word(fc_m5));
		strcat(fc_buf, " trillion ");
	}

	if (fc_m4)
	{
		strcat(fc_buf, fc_num_word(fc_m4));
		strcat(fc_buf, " billion ");
	}

	if (fc_m3)
	{
		strcat(fc_buf, fc_num_word(fc_m3));
		strcat(fc_buf, " million ");
	}

	if (fc_m2)
	{
		strcat(fc_buf, fc_num_word(fc_m2));
		strcat(fc_buf, " thousand ");
	}

	if (fc_m1)
		strcat(fc_buf, fc_num_word(fc_m1));

	if (!*fc_p)
		strcat(fc_buf, "zero");

	strcat(fc_buf, (fc_val / 100) == 1 ? " dollar and " : " dollars and ");
	strcat(fc_buf, fc_num_word(fc_m0));
	strcat(fc_buf, fc_m0 == 1 ? " cent" : " cents");

	/* 大写输出 */
	fc_buf[0] = pg_toupper((unsigned char) fc_buf[0]);

	/* 返回为文本数据 */
	PG_RETURN_TEXT_P(cstring_to_text(fc_buf));
}


/* cash_numeric()  
 * 将现金转换为数字。  
 */  
Datum cash_numeric(PG_FUNCTION_ARGS)
{
	Cash		fc_money = PG_GETARG_CASH(0);
	Datum		fc_result;
	int			fc_fpoint;
	struct lconv *fc_lconvert = PGLC_localeconv();

	/* 参见 cash_in() 中关于 frac_digits 的注释 */
	fc_fpoint = fc_lconvert->frac_digits;
	if (fc_fpoint < 0 || fc_fpoint > 10)
		fc_fpoint = 2;

	/* 将整数字面值转换为数字 */  
	fc_result = NumericGetDatum(int64_to_numeric(fc_money));

	/* 如有必要，适当缩放 */  
	if (fc_fpoint > 0)
	{
		int64		fc_scale;
		int			fc_i;
		Datum		fc_numeric_scale;
		Datum		fc_quotient;

		/* 计算所需的缩放因子 */  
		fc_scale = 1;
		for (fc_i = 0; fc_i < fc_fpoint; fc_i++)
			fc_scale *= 10;
		fc_numeric_scale = NumericGetDatum(int64_to_numeric(fc_scale));

		
/*
		 * 给定接近 INT64_MAX 的整数输入，select_div_scale()
		 * 可能选择零的结果比例，这会导致商的小数位丢失。
		 * 我们可以通过将任一输入的 dscale 设置为至少与所需
		 * 的结果比例一样大来确保结果的准确性。numeric_round()
		 * 会为我们做到这一点。
		 */
		fc_numeric_scale = DirectFunctionCall2(numeric_round,
											fc_numeric_scale,
											Int32GetDatum(fc_fpoint));

		/* 现在我们可以安全地进行除法... */
		fc_quotient = DirectFunctionCall2(numeric_div, fc_result, fc_numeric_scale);

		/* ...并强制将结果四舍五入到确切的位数 */
		fc_result = DirectFunctionCall2(numeric_round,
									 fc_quotient,
									 Int32GetDatum(fc_fpoint));
	}

	PG_RETURN_DATUM(fc_result);
}

/* numeric_cash()
 * 将 numeric 转换为现金。
 */
Datum numeric_cash(PG_FUNCTION_ARGS)
{
	Datum		fc_amount = PG_GETARG_DATUM(0);
	Cash		fc_result;
	int			fc_fpoint;
	int64		fc_scale;
	int			fc_i;
	Datum		fc_numeric_scale;
	struct lconv *fc_lconvert = PGLC_localeconv();

	/* 参见 cash_in() 中关于 frac_digits 的注释 */
	fc_fpoint = fc_lconvert->frac_digits;
	if (fc_fpoint < 0 || fc_fpoint > 10)
		fc_fpoint = 2;

	/* 计算所需的缩放因子 */  
	fc_scale = 1;
	for (fc_i = 0; fc_i < fc_fpoint; fc_i++)
		fc_scale *= 10;

	/* 将输入金额乘以比例因子 */
	fc_numeric_scale = NumericGetDatum(int64_to_numeric(fc_scale));
	fc_amount = DirectFunctionCall2(numeric_mul, fc_amount, fc_numeric_scale);

	/* 注意 numeric_int8 会为我们四舍五入到最近的整数 */
	fc_result = DatumGetInt64(DirectFunctionCall1(numeric_int8, fc_amount));

	PG_RETURN_CASH(fc_result);
}

/* int4_cash()
 * 将 int4 (int) 转换为现金
 */
Datum int4_cash(PG_FUNCTION_ARGS)
{
	int32		fc_amount = PG_GETARG_INT32(0);
	Cash		fc_result;
	int			fc_fpoint;
	int64		fc_scale;
	int			fc_i;
	struct lconv *fc_lconvert = PGLC_localeconv();

	/* 参见 cash_in() 中关于 frac_digits 的注释 */
	fc_fpoint = fc_lconvert->frac_digits;
	if (fc_fpoint < 0 || fc_fpoint > 10)
		fc_fpoint = 2;

	/* 计算所需的缩放因子 */  
	fc_scale = 1;
	for (fc_i = 0; fc_i < fc_fpoint; fc_i++)
		fc_scale *= 10;

	/* 计算 amount * scale，检查溢出 */
	fc_result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(fc_amount),
											   Int64GetDatum(fc_scale)));

	PG_RETURN_CASH(fc_result);
}

/* int8_cash()
 * 将 int8 (bigint) 转换为现金
 */
Datum int8_cash(PG_FUNCTION_ARGS)
{
	int64		fc_amount = PG_GETARG_INT64(0);
	Cash		fc_result;
	int			fc_fpoint;
	int64		fc_scale;
	int			fc_i;
	struct lconv *fc_lconvert = PGLC_localeconv();

	/* 参见 cash_in() 中关于 frac_digits 的注释 */
	fc_fpoint = fc_lconvert->frac_digits;
	if (fc_fpoint < 0 || fc_fpoint > 10)
		fc_fpoint = 2;

	/* 计算所需的缩放因子 */  
	fc_scale = 1;
	for (fc_i = 0; fc_i < fc_fpoint; fc_i++)
		fc_scale *= 10;

	/* 计算 amount * scale，检查溢出 */
	fc_result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(fc_amount),
											   Int64GetDatum(fc_scale)));

	PG_RETURN_CASH(fc_result);
}
